11. Crazyflie Backyard Flyer

Backyard Flyer Modifications

Just like with the Intel Aero and the simulator, the crazyflie is also able to be controlled (to an extent) through the Udacidrone API.

We will need to make the following modifications to our backyard flyer script to be able to control our crazyflie:

  1. update the connection
  2. modify arming and disarming flow
  3. update waypoints
  4. update takeoff target altitude
  5. update waypoint acceptance thresholds

0. Upgrade udacidrone

You'll want to make sure you have the latest version of udacidrone. You can install the latest version by activating your virtual environment and then doing a pip install -U git+https://github.com/udacity/udacidrone.git

1. Update Connection

The crazyflie's default firmware uses it's own communication protocol, Crazy RealTime Protocol (CRTP), instead of Mavlink, therefore we will need to change to using the CrazyflieConnection that can be found in Udacidrone starting with version 0.3.0 (NOTE: you may have to update your version of Udacidrone).

  1. add an import for CrazyflieConnection
from udacidrone.connection import CrazyflieConnection
  1. At the bottom of the backyard_flyer.py script, we will replace our connection object
# replace 
conn = MavlinkConnection('tcp:{0}:{1}'.format(args.host, args.port))

# with
conn = CrazyflieConnection('radio://0/80/2M')

This will create a connection to the crazyflie. The input string is the URI of the crazyflie, which is defined as a string formated as 'radio://interface id/interface channel/interface speed. For our setup, we have kept the default interface id and interface channel, but have increased the speed from the default value of 250K to 2M. Each of these values can be adjusted through the crazyflie desktop client. In an area with multiple crazyflie's flying around, it is best to change the interface id and channel to make sure each crazyflie is unique.

2. Modify Arming and Disarming

The idea of being armed or in guided mode does not exist with the crazyflie, therefore we will need to modify those parts of the flow of the script. Even more generally, the concept of state for the crazyflie is very different than what is used with the simulator or with PX4. As a result, the state_callback() callback will never be called! Since the state callback was responsible for takeoff transition, we will need to modify another one of the callbacks to handle that purpose.

Arming / Takeoff

We will choose to augment the local position callback with code needed to be able to issue the takeoff command as needed, by adding the following code to the top of the callback:

def local_position_callback(self)

    if self.flight_state == States.MANUAL:
        self.takeoff_transition()

    ...

Once the first local position message is received the takeoff command will be issued, since the drone starts in the MANUAL state. Note that we've made the takeoff altitude much smaller than what was used in the simulator since we will be flying in an indoor environment.

Disarming / Ending Mission

The same problem exists at the other end of the flight; with no armed and guided information, we don't know when to consider the flight complete and the mission over. Instead we will use the landing condition as the end of the flight. To do this, we will REPLACE velocity_callback() with the following:

def velocity_callback(self):
    if self.flight_state == States.LANDING:
        if abs(self.local_position[2] < 0.01):
            self.manual_transition()

We will still use the manual transition function as it contains all the code to consider the flight as completed and stop the connection and the script.

3. Update Waypoints

In the simulator, we had the ability to reset our "zero" position for the world to our current position, allowing us to command a 10 meter box with fairly straightforward commands. This is not possible with the crazyflie, so we will modify the coordinates of the box to take into account the position of the drone at takeoff (when the box coordinates are created). To do this, we will modify calculate_box() to be:

def calculate_box(self):
    cp = self.local_position
    cp[2] = 0
    local_waypoints = [cp + [1.0, 0.0, 0.5], cp + [1.0, 1.0, 0.5], cp + [0.0, 1.0, 0.5], cp + [0.0, 0.0, 0.5]]
    return local_waypoints

Notice we've set each of the coordinates to simply be shifted by the current position of the crazyflie when the calculate box function is run. Also notice, since we are flying out crazyflie inside, we have changed the coordinates to be a much smaller box, here a 1m box at an altitude of 0.5m.

4. Update Takeoff Target Altitude

For that same safety reason, we've also changed the target_altitude in the takeoff transition to match the 0.5m flight altitude for the box. If you have your own crazyflie and are planning to fly it indoors, we highly recommend changing the size of your box to make sure it can fly comfortably in the space you are using!

5. Update Waypoint Acceptance Thresholds

When flying the backyard flyer in the simulator, the box was 10 meters on each side, so an acceptance threshold for a waypoint of 1 meter was reasonable. Now that we've reduced the box size to something more appropriate for inside (e.g. 1 meter on a side), we need to update the acceptance thresholds for waypoints to ensure the drone doesn't preemptively transition from one waypoint to another.

In the local_position() callback, you can change the waypoint acceptance threshold by changing the following line:

def local_position_callback(self):
    ...
    # change this line:
    if np.linalg.norm(self.target_position[0:2] - self.local_position[0:2]) < 1.0:
        ...

    # to this line:
    if np.linalg.norm(self.target_position[0:2] - self.local_position[0:2]) < 0.2:
        ...

You may find you also need to change it for the acceptance on velocity to trigger than landing transition:

def local_position_callback(self):
    ...
    # change this line:
    if np.linalg.norm(self.local_velocity[0:2]) < 1.0:
        ...

    # to this line:
    if np.linalg.norm(self.local_velocity[0:2]) < 0.5:
        ...

Note here we've adjusted the threshold to 0.2 meters for waypoints and 0.5 m/s for landing. You may find you need to play around with that threshold based on the size of the box you are flying.

Flying!

And now we are ready to fly our crazyflie! To run the script we will:

  1. Plug in the crazyflie and set it on the ground, with the front pointed in the desired direction. This starting position is the bottom left hand corner of our box, so we will make sure to point it towards our open space designated for flying.

  2. Plug in the Crazyradio into the computer

  3. Make sure you're in the fcnd workspace, and then run the script, python backyard_flyer.py

  4. We will see the connection complete, a short pause (this is for the sensors to calibrate) and then the crazyflie should takeoff and complete the mission!